/*
 * Copyright (c) 2017, MegaEase
 * All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package spec

import (
	"fmt"
)

type (
	// FSM function is a finite state machine for managing faas function.
	FSM struct {
		currentState State
	}

	// Event is the event type generated by CLI or FaaSProvider.
	Event string

	// State is the FaaSFunction's state.
	State string

	// transition builds a role for state changing
	transition struct {
		From  State
		Event Event
		To    State
	}
)

const (
	// State value of FaaSFunction

	// FailedState is the failed state
	FailedState State = "failed"
	// InitialState is the init state
	InitialState State = "initial"
	// ActiveState is the active state
	ActiveState State = "active"
	// InactiveState is the inactive state
	InactiveState State = "inactive"

	// DestroyedState - only for keep fsm working
	DestroyedState State = "destroyed"

	// Function event invoked by APIs.

	// CreateEvent is the create event
	CreateEvent Event = "create"
	// StartEvent is the start event
	StartEvent Event = "start"
	// StopEvent is the stop event
	StopEvent Event = "stop"
	// UpdateEvent is the update event
	UpdateEvent Event = "update"
	// DeleteEvent is the delete event
	DeleteEvent Event = "delete"

	// Function Event invoked by FaaSProvider

	// ReadyEvent is the ready event
	ReadyEvent Event = "ready"
	// PendingEvent is the pending event
	PendingEvent Event = "pending"
	// ErrorEvent is the error event
	ErrorEvent Event = "error"
)

var (
	validState = map[State]struct{}{
		InitialState:   {},
		ActiveState:    {},
		InactiveState:  {},
		FailedState:    {},
		DestroyedState: {},
	}

	validEvent = map[Event]struct{}{
		UpdateEvent:  {},
		DeleteEvent:  {},
		StopEvent:    {},
		StartEvent:   {},
		CreateEvent:  {},
		PendingEvent: {},
		ErrorEvent:   {},
		ReadyEvent:   {},
	}

	transitions = map[Event][]transition{}
)

func init() {
	table := []transition{
		{InitialState, UpdateEvent, InitialState},
		{InitialState, DeleteEvent, DestroyedState},
		{InitialState, ReadyEvent, ActiveState},
		{InitialState, PendingEvent, InitialState},
		{InitialState, ErrorEvent, FailedState},

		{ActiveState, StopEvent, InactiveState},
		{ActiveState, ErrorEvent, FailedState},
		{ActiveState, ReadyEvent, ActiveState},
		{ActiveState, PendingEvent, FailedState},

		{InactiveState, UpdateEvent, InitialState},
		{InactiveState, StartEvent, InactiveState},
		{InactiveState, DeleteEvent, DestroyedState},
		{InactiveState, ReadyEvent, ActiveState},
		{InactiveState, PendingEvent, FailedState},
		{InactiveState, ErrorEvent, FailedState},

		{FailedState, DeleteEvent, DestroyedState},
		{FailedState, UpdateEvent, InitialState},
		{FailedState, ReadyEvent, InitialState},
		{FailedState, ErrorEvent, FailedState},
		{FailedState, PendingEvent, FailedState},
	}

	// using Event as the key
	for _, t := range table {
		transitions[t.Event] = append(transitions[t.Event], t)
	}
}

// InitState returns the initial FSM state which is the `pending` state.
func InitState() State {
	return InitialState
}

// InitFSM creates a finite state machine by given states
func InitFSM(state State) (*FSM, error) {
	if _, exist := validState[state]; !exist {
		return nil, fmt.Errorf("invalid state: %s", state)
	}
	return &FSM{
		currentState: state,
	}, nil
}

// Next turns the function status into properate state by given event.
func (fsm *FSM) Next(event Event) error {
	if _, exist := validEvent[event]; !exist {
		return fmt.Errorf("unknown event: %s", event)
	}

	if t, exist := transitions[event]; exist {
		for _, v := range t {
			if fsm.currentState == v.From {
				fsm.currentState = v.To
				return nil
			}
		}
	}
	return fmt.Errorf("invalid event: %s, currentState: %s", event, fsm.currentState)
}

// Current gets FSM current state.
func (fsm *FSM) Current() State {
	return fsm.currentState
}
